﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading;
using System.Xml;
using Framework.Atributos;
using System.Globalization;

namespace Framework.Modelo
{
    public static class Reflection
    {
        public static bool Equals(IObjeto objeto1, IObjeto objeto2)
        {
            bool resultado = true;
            Collection<PropertyInfo> propiedades1 = ObtenerPropiedades(objeto1);
            Collection<PropertyInfo> propiedades2 = ObtenerPropiedades(objeto2);
            foreach(PropertyInfo pi1 in propiedades1)
            {
                foreach (PropertyInfo pi2 in propiedades2)
                {
                    if (pi1.Name == pi2.Name)
                    {
                        object value1 = pi1.GetValue(objeto1, null);
                        object value2 = pi2.GetValue(objeto2, null);
                        if (!value1.Equals(value2))
                        {
                            resultado = false;
                            break;
                        }
                    }
                }
            }
            return resultado;
        }
        public static Collection<PropertyInfo> ObtenerPropiedades(IObjeto objeto)
        {
            Collection<PropertyInfo> resultado = new Collection<PropertyInfo>();
            if (objeto != null)
                foreach (PropertyInfo pi in objeto.GetType().GetProperties())
                {
                    if (Reflection.SePuedeConvertir(pi))
                        resultado.Add(pi);
                }
            return resultado;
        }

        public static bool SePuedeConvertir(PropertyInfo pi)
        {
            bool resultado = false;
            Collection<Attribute> atributos = ObtenerAtributos(pi);
            if (pi != null && atributos != null)
                foreach (Attribute a in atributos)
                    if (a.GetType() == typeof(PersistenciaAttribute))
                    {
                        resultado = true;
                        break;
                    }
            return resultado;
        }

        public static Collection<Attribute> ObtenerAtributos(PropertyInfo pi)
        {
            Collection<Attribute> resultado = new Collection<Attribute>();
            if (pi != null)
            {
                object[] atributos = pi.GetCustomAttributes(typeof(PersistenciaAttribute), true);
                if (atributos != null)
                    foreach (object o in atributos)
                    {
                        resultado.Add((Attribute)o);
                    }
            }
            return resultado;
        }

        public static Object ObtenerObjeto(XmlNode xn)
        {
            // create a dynamic assembly and module 
            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = "tmpAssembly";
            AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            ModuleBuilder module = assemblyBuilder.DefineDynamicModule("tmpModule");

            // create a new type builder
            TypeBuilder typeBuilder = module.DefineType(xn.Name, TypeAttributes.Public | TypeAttributes.Class);

            // Loop over the attributes that will be used as the properties names in out new type
            foreach (XmlNode node in xn.ChildNodes)
            {
                if (node.HasChildNodes && node.ChildNodes.Count > 1)
                {
                    Object propOb = ObtenerObjeto(node);
                    ObtenerPropiedad(typeBuilder, propOb);
                }
                else if (node.HasChildNodes)
                    ObtenerPropiedad(typeBuilder, node);
            }

            // Generate our type
            Type generetedType = typeBuilder.CreateType();

            // Now we have our type. Let's create an instance from it:
            object generetedObject = Activator.CreateInstance(generetedType);

            // Loop over all the generated properties, and assign the values from our XML:
            PropertyInfo[] properties = generetedType.GetProperties();

            int propertiesCounter = 0;

            // Loop over the values that we will assign to the properties
            foreach (XmlNode node in xn.ChildNodes)
            {
                if (node.HasChildNodes && node.ChildNodes.Count > 1)
                {
                    Object objeto = ObtenerObjeto(node);
                    properties[propertiesCounter].SetValue(
                        generetedObject, objeto, 
                        null);
                }
                else
                {
                    string value = node.InnerText;
                    properties[propertiesCounter].SetValue(generetedObject, value, null);
                }
                propertiesCounter++;
            }

            //Yoopy ! Return our new genereted object.
            return generetedObject;
        }

        private static void ObtenerPropiedad(TypeBuilder typeBuilder, object propOb)
        {
            //string propertyName = node.Attributes["name"].Value;
            string propertyName = propOb.GetType().FullName;

            // Generate a private field
            FieldBuilder field = typeBuilder.DefineField("_" + propertyName, propOb.GetType(), FieldAttributes.Public);
            // Generate a public property
            PropertyBuilder property =
                typeBuilder.DefineProperty(propertyName,
                                 PropertyAttributes.None,
                                 propOb.GetType(),
                                 new Type[] { propOb.GetType() });

            // The property set and property get methods require a special set of attributes:

            MethodAttributes GetSetAttr =
                MethodAttributes.Public |
                MethodAttributes.HideBySig;

            // Define the "get" accessor method for current private field.
            MethodBuilder currGetPropMthdBldr =
                typeBuilder.DefineMethod("get_value",
                                           GetSetAttr,
                                           propOb.GetType(),
                                           Type.EmptyTypes);

            // Intermediate Language stuff...
            ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
            currGetIL.Emit(OpCodes.Ldarg_0);
            currGetIL.Emit(OpCodes.Ldfld, field);
            currGetIL.Emit(OpCodes.Ret);

            // Define the "set" accessor method for current private field.
            MethodBuilder currSetPropMthdBldr =
                typeBuilder.DefineMethod("set_value",
                                           GetSetAttr,
                                           null,
                                           new Type[] { propOb.GetType() });

            // Again some Intermediate Language stuff...
            ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
            currSetIL.Emit(OpCodes.Ldarg_0);
            currSetIL.Emit(OpCodes.Ldarg_1);
            currSetIL.Emit(OpCodes.Stfld, field);
            currSetIL.Emit(OpCodes.Ret);

            // Last, we must map the two methods created above to our PropertyBuilder to 
            // their corresponding behaviors, "get" and "set" respectively. 
            property.SetGetMethod(currGetPropMthdBldr);
            property.SetSetMethod(currSetPropMthdBldr);
        }

        private static void ObtenerPropiedad(TypeBuilder typeBuilder, XmlNode node)
        {
            //string propertyName = node.Attributes["name"].Value;
            string propertyName = node.Name;

            // Generate a private field
            FieldBuilder field = typeBuilder.DefineField("_" + propertyName, typeof(string), FieldAttributes.Private);
            // Generate a public property
            PropertyBuilder property =
                typeBuilder.DefineProperty(propertyName,
                                 PropertyAttributes.None,
                                 typeof(string),
                                 new Type[] { typeof(string) });

            // The property set and property get methods require a special set of attributes:

            MethodAttributes GetSetAttr =
                MethodAttributes.Public |
                MethodAttributes.HideBySig;

            // Define the "get" accessor method for current private field.
            MethodBuilder currGetPropMthdBldr =
                typeBuilder.DefineMethod("get_value",
                                           GetSetAttr,
                                           typeof(string),
                                           Type.EmptyTypes);

            // Intermediate Language stuff...
            ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
            currGetIL.Emit(OpCodes.Ldarg_0);
            currGetIL.Emit(OpCodes.Ldfld, field);
            currGetIL.Emit(OpCodes.Ret);

            // Define the "set" accessor method for current private field.
            MethodBuilder currSetPropMthdBldr =
                typeBuilder.DefineMethod("set_value",
                                           GetSetAttr,
                                           null,
                                           new Type[] { typeof(string) });

            // Again some Intermediate Language stuff...
            ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
            currSetIL.Emit(OpCodes.Ldarg_0);
            currSetIL.Emit(OpCodes.Ldarg_1);
            currSetIL.Emit(OpCodes.Stfld, field);
            currSetIL.Emit(OpCodes.Ret);

            // Last, we must map the two methods created above to our PropertyBuilder to 
            // their corresponding behaviors, "get" and "set" respectively. 
            property.SetGetMethod(currGetPropMthdBldr);
            property.SetSetMethod(currSetPropMthdBldr);
        }

    }
}
